//
//  KDJ.swift
//  GomroClient
//
//  Created by xiaogxjkz on 2021/4/19.
//  Copyright © 2021 cn.gomro.app. All rights reserved.
//

import Foundation


/// KDJ 指标又叫随机指标
/// https://blog.csdn.net/yuhk231/article/details/80810289
struct KDJ {
    
    enum kpiType:String {
        case k = "K"
        case d = "D"
        case j = "J"
    }
    
    
    /// 计算KDJ
    /// - Parameters:
    ///   - d: 数据源
    ///   - n: 计算rsv的时间窗口
    ///   - m1: 计算K的时间窗口
    ///   - m2: 计算D的时间窗口
    /// - Returns: [String:[Double]]
    static func calcKDJFor(Data d:[[String:Any]],
                           andNumber n:Int,
                           andK m1:Int,
                           withD m2:Int) -> [String:[Double]] {
        let rsv = self.calcRSVFor(Data: d, andNumber: n)
        let k = self.calcSMAFor(Data: rsv, andNumber: m1, withWeight: 1)
        let d = self.calcSMAFor(Data: k, andNumber: m2, withWeight: 1)
        
        var j = [Double]()
        for i in 0..<k.count where i < d.count {
            j.append(3 * k[i] - 2 * d[i])
        }
        
        return [
            KDJ.kpiType.k.rawValue:k,
            KDJ.kpiType.d.rawValue:d,
            KDJ.kpiType.j.rawValue:j,
        ]
    }
    
    
    static func calcKDJFor(Model d:[GameMarketModel],
                           andNumber n:Int,
                           andK m1:Int,
                           withD m2:Int) -> [String:[Double]] {
        let rsv = self.calcRSVFor(Model: d, andNumber: n)
        let k = self.calcSMAFor(Data: rsv, andNumber: m1, withWeight: 1)
        let d = self.calcSMAFor(Data: k, andNumber: m2, withWeight: 1)
        
        var j = [Double]()
        for i in 0..<k.count where i < d.count {
            j.append(3 * k[i] - 2 * d[i])
        }
        
        return [
            KDJ.kpiType.k.rawValue:k,
            KDJ.kpiType.d.rawValue:d,
            KDJ.kpiType.j.rawValue:j,
        ]
    }
}


extension KDJ {
    
    
    /// 计算最小值
    /// - Parameters:
    ///   - d: 数据源
    ///   - n: 取最小值范围周期
    ///   - p: 最新值索引
    ///   - f: 计算字段配置
    /// - Returns: Double
    private static func calcLLVFor(Data d:[[String:Any]],
                                   andNmbner n:Int,
                                   andPos p:Int,
                                   withFiled f:String? = "Close") -> Double {
        
        if f != nil && f != "" {
            let newN = n - 1
            var l = p - newN
            if l < 0 { l = 0 }
            
            var min = Double("\(d[p][f!] ?? 0.0)")!
            var i = p
            while i >= l {
                if min > Double("\(d[i][f!] ?? 0.0)")! {
                    min = Double("\(d[i][f!] ?? 0.0)")!
                }
                
                i -= 1
            }
            
            return min
        }
        else{
            return Double.zero
        }
    }
    
    private static func calcLLVFor(Model d:[GameMarketModel],
                                   andNmbner n:Int,
                                   andPos p:Int) -> Double {
        
        let newN = n - 1
        var l = p - newN
        if l < 0 { l = 0 }
        
        var min = Double(d[p].low ?? "0.0")!
        var i = p
        while i >= l {
            if min > Double(d[i].low ?? "0.0")! {
                min = Double(d[i].low ?? "0.0")!
            }
            
            i -= 1
        }
        
        return min
    }
    
    
    /// 计算最大值
    /// - Parameters:
    ///   - d: 数据源
    ///   - n: 取最小值范围周期
    ///   - p: 最新值索引
    ///   - f: 计算字段配置
    /// - Returns: Double
    private static func calcHHV(Data d:[[String:Any]],
                                andNmbner n:Int,
                                andPos p:Int,
                                withFiled f:String? = "Close") -> Double {
        if f != nil && f != "" {
            let newN = n - 1
            var l = p - newN
            if l < 0 { l = 0 }
            
            var max = Double("\(d[p][f!] ?? 0.0)")!
            
            var i = p
            while i >= l {
                if max < Double("\(d[i][f!] ?? 0.0)")! {
                    max = Double("\(d[i][f!] ?? 0.0)")!
                }
                
                i -= 1
            }
            
            return max
        }
        else{
            return Double.zero
        }
    }
    
    private static func calcHHV(Model d:[GameMarketModel],
                                andNmbner n:Int,
                                andPos p:Int) -> Double {
        let newN = n - 1
        var l = p - newN
        if l < 0 { l = 0 }
        
        var max = Double(d[p].high ?? "0.0")!
        
        var i = p
        while i >= l {
            if max < Double(d[i].high ?? "0.0")! {
                max = Double(d[i].high ?? "0.0")!
            }
            
            i -= 1
        }
        
        return max
    }
    
    
    /// 计算SMA，加权移动平均指标
    /// - Parameters:
    ///   - d: 输入数据
    ///   - n: 时间窗口
    ///   - m: 权重
    /// - Returns: [Double]
    private static func calcSMAFor(Data d:[Double],
                                   andNumber n:Int,
                                   withWeight m:Int) -> [Double] {
        var sma:[Double] = [100]
        for i in 1..<d.count {
            let nv = d[i] * Double(m) + sma[i - 1] * Double(n - m)
            sma.append(nv/Double(n))
        }
        
        return sma
    }
    
    
    /// 计算RSV，未成熟随机值指标
    /// - Parameters:
    ///   - d: 输入数据
    ///   - n: 时间窗口
    private static func calcRSVFor(Data d:[[String:Any]],
                                   andNumber n:Int) -> [Double] {
        var rsv = [Double]()
        
        for i in 0..<d.count {
            let low = calcLLVFor(Data: d, andNmbner: n, andPos: i, withFiled: "Low")
            
            let close = Double("\(d[i]["Close"] ?? 0.0)")!
            let hight = calcHHV(Data: d, andNmbner: n, andPos: i, withFiled: "High")
            let tempValue:Double = 100 * (close - low) / (hight - low)
            rsv.append(tempValue)
        }
        
        return rsv
    }
    
    private static func calcRSVFor(Model d:[GameMarketModel],
                                   andNumber n:Int) -> [Double] {
        var rsv = [Double]()
        
        for i in 0..<d.count {
            let low = calcLLVFor(Model: d, andNmbner: n, andPos: i)
            
            let close = Double(d[i].close ?? "0.0")!
            let hight = calcHHV(Model: d, andNmbner: n, andPos: i)
            let tempValue:Double = 100 * (close - low) / (hight - low)
            rsv.append(tempValue)
        }
        
        return rsv
    }
}
